/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Maps;

import java.io.ByteArrayInputStream;
import java.io.DataOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Vector;
import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;
import javax.microedition.lcdui.Image;
import microfloat.*;
import org.kxml.Xml;
import org.kxml.kdom.Document;
import org.kxml.parser.*;
import org.kxml.kdom.*;

/**
 *
 * @author Stanley
 */


public class GoogleMaps {

    private static final String URL_UNRESERVED =
            "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
            + "abcdefghijklmnopqrstuvwxyz"
            + "0123456789-_.~";
    private static final char[] HEX = "0123456789ABCDEF".toCharArray();
    // these 2 properties will be used with map scrolling methods. You can remove them if not needed
    public static final int offset = 268435456;
    public static final double radius = offset / Math.PI;

    public GoogleMaps() {
    }

    public double[] geocodeAddress(String address) throws Exception {
        byte[] res = loadHttpFile(getGeocodeUrl(address));
        String[] data = split(new String(res), ',');

        if (!data[0].equals("200")) {
            int errorCode = Integer.parseInt(data[0]);
            throw new Exception("Google maps exception : " + getGeocodeError(errorCode));
        }

        return new double[]{
                    Double.parseDouble(data[2]), Double.parseDouble(data[3])
                };
    }

    private String getGeocodeUrl(String address) {
        return "http://maps.google.com/maps/geo?q=" + urlEncode(address) + "&output=csv";
    }

    private static String urlEncode(String str) {
        StringBuffer buf = new StringBuffer();
        byte[] bytes = null;
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            DataOutputStream dos = new DataOutputStream(bos);
            dos.writeUTF(str);
            bytes = bos.toByteArray();
        } catch (Exception e) {
            System.out.println(e.toString());
        }

        for (int i = 2; i < bytes.length; i++) {
            byte b = bytes[i];
            if (URL_UNRESERVED.indexOf(b) >= 0) {
                buf.append((char) b);
            } else {
                buf.append('%').append(HEX[(b >> 4) & 0x0f]).append(HEX[b & 0x0f]);
            }
        }

        return buf.toString();
    }

    private static byte[] loadHttpFile(String url) throws IOException {
        byte[] byteBuffer;

        HttpConnection hc = (HttpConnection) Connector.open(url);
        try {
            hc.setRequestMethod(HttpConnection.GET);
            InputStream is = hc.openInputStream();
            try {
                int len = (int) hc.getLength();
                if (len > 0) {
                    byteBuffer = new byte[len];
                    int done = 0;
                    while (done < len) {
                        done += is.read(byteBuffer, done, len - done);
                    }
                } else {
                    ByteArrayOutputStream bos = new ByteArrayOutputStream();
                    byte[] buffer = new byte[512];
                    int count;
                    while ((count = is.read(buffer)) >= 0) {
                        bos.write(buffer, 0, count);
                    }
                    byteBuffer = bos.toByteArray();
                }
            } finally {
                is.close();
            }
        } finally {
            hc.close();
        }

        return byteBuffer;
    }

    private static String[] split(String s, int chr) {
        Vector res = new Vector();

        int curr;
        int prev = 0;

        while ((curr = s.indexOf(chr, prev)) >= 0) {
            res.addElement(s.substring(prev, curr));
            prev = curr + 1;
        }

        res.addElement(s.substring(prev));

        String[] splitted = new String[res.size()];
        res.copyInto(splitted);

        return splitted;
    }

    private static String getGeocodeError(int errorCode) {
        switch (errorCode) {
            case 400:
                return "Bad request";
            case 500:
                return "Server error";
            case 601:
                return "Missing query";
            case 602:
                return "Unknown address";
            case 603:
                return "Unavailable address";
            case 604:
                return "Unknown directions";
            case 620:
                return "Too many queries";
            default:
                return "Generic error";
        }
    }

    public Image retrieveStaticImage(int width, int height, double lat,
            double lng, int zoom, String format, String maptype) throws IOException {
        byte[] imageData = loadHttpFile(getMapUrl(width, height, lng, lat, zoom, format, maptype));
        return Image.createImage(imageData, 0, imageData.length);
    }

    private String getMapUrl(int width, int height, double lng,
            double lat, int zoom, String format, String maptype) {
        return "http://maps.googleapis.com/maps/api/staticmap?center=" + lat + "," + lng + 
                "&markers=color:blue|label:S|" + lat + "," + lng + "&format="
                + format + "&zoom=" + zoom + "&size=" + width + "x" + height + "&maptype=" + maptype + "&sensor=false";
    }
    
    private String getMapUrl(int width, int height, Vector coordinates, int zoom, String maptype) {
        String coordiantesString = "";
        for (int i = 0; i < coordinates.size(); i++) {
            Coordinate c = (Coordinate)coordinates.elementAt(i);
            coordiantesString += "|" + c.getLatitude() + "," + c.getLongitude();            
        }
        
        Coordinate begin = (Coordinate)coordinates.elementAt(0);
        Coordinate end = (Coordinate)coordinates.elementAt(coordinates.size() - 1);
        String markerString = "&markers=color:blue|label:S|" + begin.getLatitude() + "," +
                begin.getLongitude() + "|" + end.getLatitude() + "," + end.getLongitude();
        
        return "http://maps.googleapis.com/maps/api/staticmap?sensor=false&format=png32&size=" + width + "x" + height +
                "&path=color:0xff0000ff|weight:5" + coordiantesString + markerString + "&enc:encoded_data";
    }
    
    private String getDirectionUrl(String sAddress, String eAddress, String travelMode) {
        //return "http://maps.google.com/maps?saddr=\"" + urlEncode(sAddress) + "\"&daddr=\"" + urlEncode(eAddress) + "\"";
        sAddress = sAddress.replace(' ', '+');
        eAddress = eAddress.replace(' ', '+');
        return "http://maps.googleapis.com/maps/api/directions/xml?origin=" + sAddress + "&destination=" + eAddress +"&travelmode=" + travelMode + "&sensor=false";
    }
    
    public Image retrieveDirection(int width, int height, String startAddress, String endAddress, String travelMode, int zoom, String maptype) throws IOException {
        //byte[] imageData = loadHttpFile(getDirectionUrl(startAddress, endAddress, travelMode));
        //return Image.createImage(imageData, 0, imageData.length); 
        
        HttpConnection hc = (HttpConnection)Connector.open(getDirectionUrl(startAddress, endAddress, travelMode));
        Document doc = new Document();
        XmlParser parser;
        // vector coordinates
        Vector coordinates = new Vector();        
        // distance : miles
        String distance = null;
        // duration
        String duration = null;
        // status
        String status = null;
        try {            
            // reponse package as xml stream
            hc.setRequestMethod(HttpConnection.POST);
            // create input stream for parse xml
            InputStream is = hc.openInputStream();
            
            InputStreamReader reader = new InputStreamReader(is);
            parser = new XmlParser(reader);
           
            doc.parse(parser);
        } catch (IOException e) {
            // report error
            System.err.println(e);
            e.printStackTrace();
            parser = null;
            //doc = null;
            return null; 
        }
        
        
        // now, we get the root element
        Element rootElement = doc.getRootElement();
        Element statusElement = rootElement.getElement("status");
        Element routeElement = rootElement.getElement("route");
        Element legElement = routeElement.getElement("leg");
        
        // get all step in leg
        int step_count = legElement.getChildCount();
        // iterate with all steps
        for (int i = 0; i < step_count; i++) {
            if(legElement.getType(i) != Xml.ELEMENT) {
                continue;
            }
            
            Element childElement = legElement.getElement(i);
            String childName = childElement.getName();
            if(childName.equals("step")) {
                // read <start_location>
                Element start = childElement.getElement("start_location");
                Coordinate c = new Coordinate(Double.parseDouble(start.getElement("lat").getText()), Double.parseDouble(start.getElement("lng").getText()));
                coordinates.addElement(c);
            } else if(childName.equals("duration")) {
                duration = childElement.getElement("text").getText();
            } else if(childName.equals("distance")) {
                distance = childElement.getElement("text").getText();
            }            
        }
        
        
        
        // get image path form google static map
        byte[] imageData = loadHttpFile(getMapUrl(width, height, coordinates, zoom, maptype));
        return Image.createImage(imageData, 0, imageData.length);
    }

    // UTILITY METHODS FOR MAP SCROLLING
    
    /***
     * create the new map center latitude and longitude
     * @param lat, lng the current center latitude and longitude coordinates
     * @param lgn longitude coordinate
     * @param deltaX and deltaY, in pixels, of new map center
     * @pram z the zoom level
     * @return new map center latitude and longitude
     * @author stanley
     * Original code, in JavaScript, is available here: http://home.provide.net/~bratliff/adjust.js
     */
    public double[] adjust(double lat, double lng, int deltaX, int deltaY, int z) {
        return new double[]{
                    YToL(LToY(lat) + (deltaY << (21 - z))),
                    XToL(LToX(lng) + (deltaX << (21 - z)))
                };
    }

    double LToX(double x) {
        return round(offset + radius * x * Math.PI / 180);
    }

    double LToY(double y) {
        return round(
                offset - radius *
                Double.longBitsToDouble(MicroDouble.log(
                Double.doubleToLongBits(
                (1 + Math.sin(y * Math.PI / 180)) / (1 - Math.sin(y * Math.PI / 180))))) / 2);        
    }

    double XToL(double x) {
        return ((round(x) - offset) / radius) * 180 / Math.PI;
    }

    double YToL(double y) {
        return (Math.PI / 2 - 2 * Double.longBitsToDouble(
                MicroDouble.atan(
                MicroDouble.exp(Double.doubleToLongBits((round(y) - offset) / radius))))) * 180 / Math.PI;
        
    }

    double round(double num) {
        double floor = Math.floor(num);
        if (num - floor >= 0.5) {
            return Math.ceil(num);
        } else {
            return floor;
        }
    }
}
